package service.associationmanager;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import service.authentification.Contributor;
import service.configuration.IConfiguration;
import service.configuration.NamingUtil;
import service.mailing.IMailer;
import service.sheetmanager.ISheetManager;
import dao.IDaoAsof;
import entities.Delegation;
import entities.OwnerAssociation;
import entities.PersonSheet;
import entities.Sheet;
import entities.Status;
import web.form.DelegationInfo;
import web.form.SheetInfo;
/**
 * <b>La classe AssociationManager regroupe toutes les méthodes permetant de récuperer les liste de fiches de la personne ,elle implemente l'interface IAssociationManager</b>
 * <p>
 * Les listes de fiches en question sont  :
 * <ul>
 * <i>Liste des fiches propriétaire</i>
 * <i>Liste des des délégations reçues</i>
 * <i>Liste des des délégations données</i>

 * </ul>
 * </p>
 */
@Service
public class AssociationManager implements IAssociationManager {
	/**
	 * 	le service de la couche d'accés au données IDaoAsof
	 * @see IDaoAsof
	 */
	@Resource
	private IDaoAsof dao;
	/**
	 * 	le service sheetManager permet de créer la vue 
	 * @see ISheetManager
	 */
	@Resource
	private ISheetManager sheetManager;
	/**
	 * 	Service Configuration 
	 * @see IConfiguration
	 */
	@Resource
	private IConfiguration conf;
	/**
	 * 	le service d'envoi de mail
	 * @see IMailer
	 */
	@Resource
	private IMailer mailer;
	
	

	
	private interface TypeGetter<T> {
		String getType(T o);
	}
	
	/*
	private interface TypeAndCodeGetter<T> extends TypeGetter<T> {
		String getCode(T o);
	}
	*/
	
	private class DelegationInfoTypeGetter implements TypeGetter<DelegationInfo> {
		public String getType(DelegationInfo delegation) {
			return delegation.getSheetType();
		}
	}
	
	private class SheetInfoTypeGetter implements TypeGetter<SheetInfo> {
		public String getType(SheetInfo sheetInfo) {
			return sheetInfo.getType();
		}
	}
	
	/*
	private class DelegationInfoCodeGetter extends DelegationInfoTypeGetter implements TypeAndCodeGetter<DelegationInfo> {
		public String getCode(DelegationInfo delegation) {
			return delegation.getSheetCode();
		}
	}
	
	private class SheetInfoCodeGetter extends SheetInfoTypeGetter implements TypeAndCodeGetter<SheetInfo> {
		public String getCode(SheetInfo sheetInfo) {
			return sheetInfo.getCode();
		}
	}
	*/
	
	
	private DelegationInfoTypeGetter delegationInfoTypeGetter;
	//private DelegationInfoCodeGetter delegationInfoCodeGetter;
	private SheetInfoTypeGetter sheetInfoTypeGetter;
	//private SheetInfoCodeGetter sheetInfoCodeGetter;
	
	
	public AssociationManager() {
		delegationInfoTypeGetter = new DelegationInfoTypeGetter();
		//delegationInfoCodeGetter = new DelegationInfoCodeGetter();
		sheetInfoTypeGetter = new SheetInfoTypeGetter();
		//sheetInfoCodeGetter = new SheetInfoCodeGetter();
	}
	
	/*
	// Expérimental
	private interface MapAffecter<T, V> {
		void affect(List<V> list, T object, TypeAndCodeGetter<T> codeGetter);
	}
	

	private class ListCodeMapAffecter<T> implements MapAffecter<T, String> {
		public void affect(List<String> list, T object, TypeAndCodeGetter<T> codeGetter) {
			list.add(codeGetter.getCode(object));
		}
	}
	
	private class ListObjectMapAffecter<T> implements MapAffecter<T, T> {
		public void affect(List<T> list, T object, TypeAndCodeGetter<T> codeGetter) {
			list.add(object);
		}
	}
	
	private <T, V> Map<String, List<V>> groupByType(List<T> objects, TypeAndCodeGetter<T> codeAndTypeGetter, MapAffecter<T, V> affecter) {
		Map<String, List<V>> map = new HashMap<String, List<V>>();
		for (T object : objects) {
			List<V> list = null;
			if (map.containsKey(codeAndTypeGetter.getType(object))) {
				list = map.get(codeAndTypeGetter.getType(object));
			} else {
				list = new ArrayList<V>();
				map.put(codeAndTypeGetter.getType(object), list);
			}
			affecter.affect(list, object, codeAndTypeGetter);
		}
		return map;
	}
	// Fin expérimental
	*/
	
	private <T> Map<String, List<T>> groupByType(List<T> objects, TypeGetter<T> codeAndTypeGetter) {
		Map<String, List<T>> typeAndObject = new HashMap<String, List<T>>();
		for (T object : objects) {
			List<T> list = null;
			if (typeAndObject.containsKey(codeAndTypeGetter.getType(object))) {
				list = typeAndObject.get(codeAndTypeGetter.getType(object));
			} else {
				list = new ArrayList<T>();
				typeAndObject.put(codeAndTypeGetter.getType(object), list);
			}
			list.add(object);
		}
		return typeAndObject;
	}
	
	/*
	private <T> Map<String, List<String>> groupByType(List<T> objects, TypeAndCodeGetter<T> codeAndTypeGetter) {
		Map<String, List<String>> typeAndObject = new HashMap<String, List<String>>();
		for (T object : objects) {
			List<String> list = null;
			if (typeAndObject.containsKey(codeAndTypeGetter.getType(object))) {
				list = typeAndObject.get(codeAndTypeGetter.getType(object));
			} else {
				list = new ArrayList<String>();
				typeAndObject.put(codeAndTypeGetter.getType(object), list);
			}
			list.add(codeAndTypeGetter.getCode(object));
		}
		return typeAndObject;
	}
	*/
	  
    /**
	 * Cette méthode regroupe une liste de delegation par type de fiche
	 * @param	delegations 
	 * 					une liste de delegations
	 * @return
	 * 			une Map avec comme clée le type et comme valeur une liste de de délegation de ce type
	 * 
	 */
	private Map<String, List<String>> groupByTypeDelegation(List<Delegation> delegations) {
		Map<String, List<String>> typeAndCode = new HashMap<String, List<String>>();
		for (Delegation delegation : delegations) {
			List<String> codes = null;
			if (typeAndCode.containsKey(delegation.getSheetType())) {
				codes = typeAndCode.get(delegation.getSheetType());
			} else {
				codes = new ArrayList<String>();
				typeAndCode.put(delegation.getSheetType(), codes);
			}
			codes.add(delegation.getSheetCode());
		}
		return typeAndCode;
	}

	 /**
		 * Cette méthode regroupe une liste de fiche propriétaire d'une personne par type 
		 * @param	owners  
		 * 					une liste de fiches propriétaire
		 * @return
		 * 			une Map avec comme clée le type et comme valeur une liste de fiche de ce type
		 * 
		 */
	private Map<String, List<String>> groupByTypeOwner(List<OwnerAssociation> owners) {
		Map<String, List<String>> typeAndCode = new HashMap<String, List<String>>();
		for (OwnerAssociation owner : owners) {
			List<String> codes = null;
			if (typeAndCode.containsKey(owner.getSheetType())) {
				codes = typeAndCode.get(owner.getSheetType());
			} else {
				codes = new ArrayList<String>();
				typeAndCode.put(owner.getSheetType(), codes);
			}
			codes.add(owner.getSheetCode());
		}
		return typeAndCode;
	}
	
	
	private List<SheetInfo> generateSheetInfos(List<OwnerAssociation> association) {
		List<SheetInfo> list = new ArrayList<SheetInfo>();
		for (OwnerAssociation asso : association) {
			SheetInfo sheet = new SheetInfo(asso.getSheetType(), asso.getSheetCode(), null, true);
			list.add(sheet);
		}
		
		return list;
	}
	
	
	
	private List<DelegationInfo> generateReceivedDelegationInfos(List<Delegation> delegation) {
		List<DelegationInfo> list = new ArrayList<DelegationInfo>();
		for (Delegation asso : delegation){
			DelegationInfo delegationInfo = new DelegationInfo(asso.getId(), 
					asso.getOwnerCode(), null,
					asso.getSheetCode(), asso.getSheetType(), null, false);
			list.add(delegationInfo);
		}
		
		return list;
	}
	
	
	private List<DelegationInfo> generateGivenDelegationInfos(List<Delegation> delegation) {
		List<DelegationInfo> list = new ArrayList<DelegationInfo>();
		for (Delegation asso : delegation){
			DelegationInfo delegationInfo = new DelegationInfo(asso.getId(), 
					asso.getPersonCode(), null,
					asso.getSheetCode(), asso.getSheetType(), null, false);
			list.add(delegationInfo);
		}
		
		return list;
	}

	
	private Map<String, PersonSheet> generatePersonSheetsMap(List<String> personCodes) {
		// on cherche les fiches des personnes pour affecter leurs noms aux DelegationInfos
		List<Sheet> personSheets = dao.findSheetsByProperty(NamingUtil.CODE, personCodes, conf.getPersonType());
		Map<String, PersonSheet> personSheetsByCode = new HashMap<String, PersonSheet>();
		for(Sheet sheet : personSheets) {
			personSheetsByCode.put(sheet.getCode(), (PersonSheet)sheet);
		}
		
		return personSheetsByCode;
	}
	
	
	/**
	 * Cette méthode prend un code d'une personne et retourne une liste de fiches qu'il a délégué
	 * @param	ownercode 
	 * 					le code du contributeur  
	 * @return La liste des fiches qu'il a délégué
	 * 		
	 */
	
	public List<DelegationInfo> findGivenDelegations(String ownerCode) {
		List<Delegation> delegations = dao.findGivenDelegations(ownerCode);
		List<DelegationInfo> delegationInfos = generateGivenDelegationInfos(delegations);
		Map<String,List<String>> sheetCodesByType = groupByTypeDelegation(delegations);

		// remplit les titres des delegationsInfos (calculé à l'aide de la sheet) :
		for(Entry<String, List<String>> e : sheetCodesByType.entrySet()){
			List<Sheet> sheets = dao.findSheetsByProperty(NamingUtil.CODE, e.getValue(), e.getKey());
			for(Sheet sheet : sheets) {	
				String title = sheetManager.generateSheetTitle(sheet);
				for(DelegationInfo delegationInfo : delegationInfos) {	
					if(sheet.getCode().equals(delegationInfo.getSheetCode())) {
						delegationInfo.setSheetTitle(title);
						delegationInfo.setValidSheet(sheet.getStatus() == Status.Valid);
					}
				}
			}
		}	
		
		// on construit la liste des codes des propriétaires de fiches
		List<String> personCodes = new ArrayList<String>();
		for(DelegationInfo delegationInfo : delegationInfos) {
			personCodes.add(delegationInfo.getPersonCode());
		}
		
		// remplit les infos sur les personnes à qui sont déléguées les fiches  : 
		Map<String, PersonSheet> personSheetsByCode = generatePersonSheetsMap(personCodes);
		for(DelegationInfo delegationInfo : delegationInfos) {
			PersonSheet personSheet = (PersonSheet)personSheetsByCode.get(delegationInfo.getPersonCode());
			delegationInfo.setPersonTitle(personSheet.getFirstName() + " " +
										  personSheet.getLastName());
		}
		
		return delegationInfos ;
	}
	
	
	
	public Map<String, List<DelegationInfo>> findGivenDelegationsByType(String ownerCode) {
		List<DelegationInfo> delegationInfos = findGivenDelegations(ownerCode);
		return groupByType(delegationInfos, delegationInfoTypeGetter);
	}
	
	
	/**
	 * Cette méthode prend un code d'une personne et retourne une liste des délégation  reçues sur des fiches 
	 * @param	PersonCode  
	 * 					le code du contributeur   
	 * @return La liste des fiches qu'il a reçu par délégation
	 * 		
	 */
	public List<DelegationInfo> findReceivedDelegations(String personCode) {
		List<Delegation> delegations = dao.findReceivedDelegations(personCode);
		List<DelegationInfo> delegationInfos = generateReceivedDelegationInfos(delegations);
		Map<String,List<String>> sheetCodesByType = groupByTypeDelegation(delegations);

		// remplit les titres des delegationsInfos (calculé à l'aide de la sheet) :
		for(Entry<String, List<String>> e : sheetCodesByType.entrySet()){
			List<Sheet> sheets = dao.findSheetsByProperty(NamingUtil.CODE, e.getValue(), e.getKey());
			for(Sheet sheet : sheets) {	
				String title = sheetManager.generateSheetTitle(sheet);
				for(DelegationInfo delegationInfo : delegationInfos) {	
					if(sheet.getCode().equals(delegationInfo.getSheetCode())) {
						delegationInfo.setSheetTitle(title);
						delegationInfo.setValidSheet(sheet.getStatus() == Status.Valid);
					}
				}
			}
		}
		
		// on construit la liste des codes des propriétaires de fiches
		List<String> personCodes = new ArrayList<String>();
		for(DelegationInfo delegationInfo : delegationInfos) {
			personCodes.add(delegationInfo.getPersonCode());
		}
		
		// remplit les infos sur les personnes propriétaires des fiches  : 
		Map<String, PersonSheet> personSheetsByCode = generatePersonSheetsMap(personCodes);
		for(DelegationInfo delegationInfo : delegationInfos) {
			PersonSheet ownerSheet = personSheetsByCode.get(delegationInfo.getPersonCode());
			delegationInfo.setPersonTitle(ownerSheet.getFirstName() + " " +
										 ownerSheet.getLastName());
		}
		
		return delegationInfos ;
	}
	
	
	
	public Map<String, List<DelegationInfo>> findReceivedDelegationsByType(String personCode) {
		List<DelegationInfo> delegationInfos = findReceivedDelegations(personCode);
		return groupByType(delegationInfos, delegationInfoTypeGetter);
	}
	

	/**
	 * Cette méthode prend un code d'une personne et retourne la liste des fiches propriétaire 
	 * @param	ownerCode  
	 * 					le code du contributeur   
	 * @return La liste des fiches dont il est propriétaire 
	 * 		
	 */
    public List<SheetInfo> findOwnedSheet(String ownerCode) {
		List<OwnerAssociation> ownerAssociations = dao.findOwnerAssociations(ownerCode);
		List<SheetInfo> sheetInfos = generateSheetInfos(ownerAssociations);
		Map<String, List<String>> sheetCodesByType = groupByTypeOwner(ownerAssociations);
		List<Sheet> sheets = new ArrayList<Sheet>();

		for(Entry<String, List<String>> e : sheetCodesByType.entrySet()) {
			sheets = dao.findSheetsByProperty(NamingUtil.CODE, e.getValue(), e.getKey());
			
			for(Sheet sheet :sheets) {	
				String title = sheetManager.generateSheetTitle(sheet);
				for(SheetInfo sheetInfo : sheetInfos) {	
					if(sheet.getCode().equals(sheetInfo.getCode())) {
						sheetInfo.setTitle(title);
						sheetInfo.setValid(sheet.getStatus() == Status.Valid);
					}
				}
			}
		}
		
        return sheetInfos;
    }
	
	
	public Map<String, List<SheetInfo>> findOwnedSheetByType(String ownerCode) {
		List<SheetInfo> sheetInfos = findOwnedSheet(ownerCode);
		return groupByType(sheetInfos, sheetInfoTypeGetter);
	}
	
	/**
	 * Cette méthode ajoute une délegation sur une liste de fiche
	 * @param	contributor
	 *   					l'objet contributeur   
	 * @param 	personCodes
	 * 						La liste des personnes a qui on veut déléguer les fiches
	 * @return retourne la liste des delegations impossible à éffectuer 
	 * 		
	 */
	public List<SheetInfo> addDelegations(Contributor contributor,	List<String> personCodes,
			List<SheetInfo> sheets) {
		boolean hasDelegation = false;
		List<Delegation>possibleDelegations = new ArrayList<Delegation>();
		List<SheetInfo>impossibleDelegations = new ArrayList<SheetInfo>();
		for(String personCode : personCodes){
			String possibleDelegationsMail="";
			for(SheetInfo sheet : sheets){
				hasDelegation =dao.personHasDelegationForSheet(personCode,sheet.getCode());
				if((!hasDelegation)&&(dao.personOwnsSheet(contributor.getCode(), sheet.getCode()))){
					Delegation delegation= new Delegation();
					delegation.setOwnerCode(contributor.getCode());
					delegation.setPersonCode(personCode);
					delegation.setSheetCode(sheet.getCode());
					delegation.setSheetType(sheet.getType());
					possibleDelegations.add(delegation);
					possibleDelegationsMail+="<li><a href=\"" +conf.getApplicationUrl()+"/sheet.htm?code="+sheet.getCode()+"&type="+ sheet.getType()+ "\">" +sheet.getCode()+" : "+sheet.getTitle() + "</a></li>";
				}else{
					impossibleDelegations.add(sheet);
				}		
			}
			if(possibleDelegationsMail.compareTo("")!=0){
				PersonSheet person = (PersonSheet)dao.findSheetByCode(personCode, conf.getPersonType());
				if(person!=null)
					mailer.sendHtmlMail(person.getMail(),
						"Délégation reçue [ASOF]",
						"<p>Bonjour "+person.getFirstName()+" "+person.getLastName()+",</p><br/>"+
						"<p>Vous venez de recevoir une délégation sur le site de l'offre de formation d'Aix-Marseille Université de la part de: \" "+contributor.getFirstName()+" "+contributor.getLastName()+" \".</p>"+
						"<p>Pour modifier les fiches :</p>"+
						"<ul>"+possibleDelegationsMail+"</ul>"+
						"<p>Le site ASOF est accessible à cette adresse : <a href=\"" + conf.getApplicationUrl() + "\">" + conf.getApplicationUrl() + "</a></p>" +
						"<p>ASOF.<p/>"		);
			}	
		}
		if(!possibleDelegations.isEmpty()){
			dao.addDelegations(possibleDelegations);
		}
		return impossibleDelegations;
	}
	/**
	 * Cette méthode supprime des délégations
	 * @param	delegationInfos	
	 * 							une liste de délégations à supprimer 
	 */
	
	public void removeDelegations(List<DelegationInfo> delegationInfos) {
		List<Long> delegationIds = new ArrayList<Long>();
		for(DelegationInfo delegationInfo : delegationInfos) {
			delegationIds.add(delegationInfo.getId());
		}
		for(Long id : delegationIds)
		{
			System.out.println(id + "*************** Dans association");
		}
		dao.removeDelegationsByIds(delegationIds);
	}


	private <T> Map<String, List<Integer>> generateGroupedIndexes(List<T> objects, TypeGetter<T> codeAndTypeGetter) {
		Map<String, List<Integer>> typeAndObject = new HashMap<String, List<Integer>>();
		for (int i = 0; i < objects.size(); i++) {
			T object = objects.get(i);
			List<Integer> list = null;
			if (typeAndObject.containsKey(codeAndTypeGetter.getType(object))) {
				list = typeAndObject.get(codeAndTypeGetter.getType(object));
			} else {
				list = new ArrayList<Integer>();
				typeAndObject.put(codeAndTypeGetter.getType(object), list);
			}
			list.add(i);
		}
		return typeAndObject;
	}
	
	
	public Map<String, List<Integer>> generateGroupedSheetInfosIndexes(List<SheetInfo> sheetInfos) {
		return generateGroupedIndexes(sheetInfos, sheetInfoTypeGetter);
	}

	
	public Map<String, List<Integer>> generateGroupedDelegationsIndexes(List<DelegationInfo> delegationInfos) {
		return generateGroupedIndexes(delegationInfos, delegationInfoTypeGetter);
	}
	
}
